using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Linq;
using System.Linq.Expressions;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Nvigorate.Common.Reflection;
using Nvigorate.Data;
using Nvigorate.Relational.Mapping;
using Nvigorate.Relational.Mapping.Index;
using Nvigorate.Relational.Navigation;
using Nvigorate.Relational.Record;
using Nvigorate.Relational.Repository;
using Nvigorate.Relational.Visitors;
using Nvigorate.TestObjects;
using Nvigorate.Extensions;

namespace Nvigorate.Test.Relational.Binding
{
    /// <summary>
    /// Summary description for LambdaBindingTests
    /// </summary>
    [TestClass]
    public class BindingLambdaFactoryTests
    {
        private static int _expectedRecords = 2000;
        private static DataFactory personDataFactory = new DataFactory(typeof(Person));
        private static IRepositoryMapIndex repositoryMapIndex;
        
        [ClassInitialize]
        public static void Init(TestContext context)
        {
            MapFake.PopulateMaps();
            repositoryMapIndex = new MapIndex().GetRepositoryMapIndex("simple");

            for (int i = 0; i < _expectedRecords; i++)
            {
                personDataFactory.GetRow();
            }
        }

        #region GetBindingLambda Tests

        [TestMethod]
        public void GetBindingLambda_NoChildObjects_BindsDataToType()
        {
            IRecord record = new DataRowRecord(GetRowForBindTarget());
            Func<IRecord, BindTarget> lambda = BindingLambdaFactory.GetBindingLambda<BindTarget>("", record);
            BindTarget target = lambda(record);

            Reflector.GetProperties(typeof (BindTarget)).ForEach(p => Assert.AreEqual(record[p],
                                                                                      Reflector.Read(target, p)));
        }

        [TestMethod]
        public void GetBindingLambda_MultipleRecords_ReusesLambda()
        {
            IRecord record1 = new DataRowRecord(GetRowForBindTarget());
            Func<IRecord, BindTarget> lambda = BindingLambdaFactory.GetBindingLambda<BindTarget>("", record1);
            BindTarget target1 = lambda(record1);

            var properties = Reflector.GetProperties(typeof (BindTarget));

            IRecord record2 = new DataRowRecord(GetRowForBindTarget());
            BindTarget target2 = lambda(record2);

            properties.ForEach(p => Assert.AreEqual(record1[p],
                                                    Reflector.Read(target1, p)));
            properties.ForEach(p => Assert.AreEqual(record2[p], 
                                                    Reflector.Read(target2, p)));
            
        }

        [TestMethod]
        public void GetBindingLambda_ExpectedRecords_BindsAll()
        {
            var type = typeof (Person);
            var records = (DataRecordCollection) personDataFactory.Table;
            Func<IRecord, Person> _bindLambda = BindingLambdaFactory.GetBindingLambda<Person>("simple", records[0]);
            var list = records.Select(_bindLambda).ToList();
            Assert.AreEqual(_expectedRecords, list.Count);
        }

        [TestMethod]
        public void GetPersistableBinder_ExpectedRecords_BindsAll()
        {
            var type = typeof(Person);
            var dataSet = new DataSet();
            dataSet.Tables.Add(personDataFactory.Table);

            var bindingVisitor = new PersistableTypeBinder<Person>(repositoryMapIndex, dataSet);
            var typeNavigator = new PersistableTypeNavigator(type, new LoadInstruction<Person>(), repositoryMapIndex);
            typeNavigator.Accept(bindingVisitor);

            var list = bindingVisitor.Instances.ToList();
            Assert.AreEqual(_expectedRecords, list.Count);
        }

        [TestMethod]
        public void TestReflectiveCachingWriter_ExpectedRecords_PreLoad()
        {
            var writer = new DynamicAccessorCache();
            var type = typeof(Person);
            writer.PreLoad(type);
            var map = repositoryMapIndex.GetMap(type);

            var records = (DataRecordCollection)personDataFactory.Table;
            var list =
                records.Select(r =>
                {
                    var instance = new Person();
                    map.GetSuppliedAssignments(r)
                        .ForEach(a =>
                        {
                            writer.Write(instance, a.Target.Field, r[a.Target]);
                        });
                    return instance;
                }).ToList();

            Assert.AreEqual(_expectedRecords, list.Count);
        }

        [TestMethod]
        [Ignore]
        // this unit test no longer works because this object model includes a back
        // reference to the parent; a feature which was unsupported in this version
        // of the binding engine
        public void GetBindingLambda_NestedObjects_BindsAllObjects()
        {
            var personFactory = new DataFactory(typeof (Person));
            var vehicleFactory = new DataFactory(typeof (Vehicle));

            personFactory.Table.TableName = "Person";
            vehicleFactory.Table.TableName = "Vehicle";

            DataRow personRow = personFactory.GetRow();

            for (int i = 0; i < 5; i ++)
            {
                var vehicleRow = vehicleFactory.GetRow();
                vehicleRow["OwnerSocial"] = personRow["SocialSecurity"];
            }

            DataTable personTable = personFactory.Table;
            DataTable vehicleTable = vehicleFactory.Table;

            var complete = new DataSet("PersonalInfo");

            complete.Tables.Add(personTable);
            complete.Tables.Add(vehicleTable);

            complete.Relations.Add("Vehicle", personTable.Columns["SocialSecurity"], vehicleTable.Columns["OwnerSocial"]);
            complete.Relations.Add("Person", vehicleTable.Columns["OwnerSocial"], personTable.Columns["SocialSecurity"]);

            IRecord record = new DataRowRecord(personRow);
            Func<IRecord, Person> binder = BindingLambdaFactory.GetBindingLambda<Person>("", record);
            Person person = binder(record);

            Assert.IsNotNull(person);
            Assert.AreEqual(person.LastName, personRow["LastName"]);
            Assert.AreEqual(person.SocialSecurity, personRow["SocialSecurity"]);
            Assert.AreEqual(person.DateOfBirth, personRow["DateOfBirth"]);
            Assert.AreEqual(person.FirstName, personRow["FirstName"]);

            Assert.AreEqual(5, person.Vehicles.Count);
            for (int i = 0; i < person.Vehicles.Count; i++)
            {
                var vehicle = person.Vehicles[i];
                var row = vehicleTable.Rows[i];
                Assert.AreEqual(vehicle.VIN, row["Vin"]);
                Assert.AreEqual(vehicle.Model, row["Model"]);
                Assert.AreEqual(vehicle.OwnerSocial, row["OwnerSocial"]);
            }
        }

        [TestMethod]
        public void RecursiveBindTest()
        {
            DataFactory factory = new DataFactory(typeof(Employee));
            var record = (DataRowRecord) factory.GetRow();
            var instructions = LoadInstruction<Person>.New().EagerLoad();
            var database = new DataInterface(ConfigurationManager.ConnectionStrings["simple"]);
            var type = typeof(Person);

            var nav = new PersistableTypeNavigator(type, instructions, repositoryMapIndex);
            var visitor = new SelectVisitor(instructions, database.Provider, repositoryMapIndex);
            nav.Accept(visitor);
            var result = visitor.GetQuery();
            var tables = visitor.TableNames;
            var set = database.GetSet(result, "set", tables, repositoryMapIndex);

            
            var builder = new PersistableTypeBinder<Person>(repositoryMapIndex, set);

            nav.Accept(builder);

            var instances = builder.Instances;
            Assert.IsNotNull(instances);
        }

        [TestMethod]
        public void TestDelegateWriteProperty()
        {
            var type = typeof (Person);
            var propertySetter = Reflector.GetPropertyInfo(type, "FirstName", true).GetSetMethod(true);
            var param1 = Expression.Parameter(type, "instance");
            var param2 = Expression.Parameter(typeof(string), "value");
            var methodCallExpression = Expression.Call(param1, propertySetter, param2);
            var lambda = Expression.Lambda(typeof(Action<Person, string>), methodCallExpression, param1, param2).Compile();

            var person = new Person();
            for (int i = 0; i < 1000; i++ )
            {
                //lambda.DynamicInvoke(person, "test");
                TestCaller(lambda, person, "test");

                Assert.AreEqual(person.FirstName, "test");
            }
        }

        public void TestCaller(Delegate call, Person instance, string argument)
        {
            ((call) as Action<Person, string>)(instance, argument);
        }

        [TestMethod]
        public void TestLambdaWriteProperty()
        {
            var type = typeof(Person);
            var propertySetter = Reflector.GetPropertyInfo(type, "FirstName", true).GetSetMethod(true);
            var param1 = Expression.Parameter(type, "instance");
            var param2 = Expression.Parameter(typeof(string), "value");
            var methodCallExpression = Expression.Call(param1, propertySetter, param2);
            var lambda = (Action<Person, string>) Expression.Lambda(typeof(Action<Person, string>), methodCallExpression, param1, param2).Compile();

            var person = new Person();
            for (int i = 0; i < 1000; i++)
            {
                lambda(person, "test");

                Assert.AreEqual(person.FirstName, "test");
            }
        }

        [TestMethod]
        public void TestReflectionWriteProperty()
        {
            var person = new Person();
            for (int i = 0; i < 1000; i++)
            {
                Reflector.Write(person, "FirstName", "test");

                Assert.AreEqual(person.FirstName, "test");
            }
        }

        [TestMethod]
        public void TestRecordKeys()
        {
            var cache = new Dictionary<IRecord, int>();

            var factory = new DataFactory(typeof (Person));

            for (int i = 0; i < 10; i++)
                factory.GetRow();

            var records = (DataRecordCollection) factory.Table;

            var keySet1 = records.Select(r => r.GetHashCode()).ToList();
            var keySet2 = records.Select(r => r.GetHashCode()).ToList();

            Assert.IsTrue(keySet1.SequenceEqual(keySet2));

            int index = 0;
            records.ForEach(r => cache.Add(r, index++));

            index = 0;
            records.ForEach(r => Assert.AreEqual(index++, cache[r]));
        }

        #endregion

        #region Private Methods

        private DataRow GetRowForBindTarget()
        {
            DataFactory factory = new DataFactory(typeof (BindTarget));
            return factory.GetRow();
        }

        #endregion
    }
}

//internal static ListInitExpression GetListBinderLambda<T>(this T instance, IRecord parent, string relationName, ParameterExpression parameter) where T : new()
//{
//    IRecord childRecord = parent.GetRelatedRecords(relationName).First();
//    var bind = GetBindingLambda<T>( new T(), childRecord);
//    MethodInfo info = Reflector.GetMethodInfo(typeof(IRecord), "GetRelatedRecords", true)[0];
//    Expression getRecordsExpression = Expression.Call(parameter, info, Expression.Constant(relationName));
//    Func<List<IRecord>, IEnumerable<T>> listBinder = list => list.Select(bind);

//    NewExpression newList = Expression.New(typeof(List<T>));

//    return Expression.ListInit(newList, Expression.Call(listBinder.Method, getRecordsExpression));
//}